/*
 * File:     wiidevicedriver.cpp
 * Authors:  Dominic Stalder, Thomas Bruederli
 *
 * Implementation of the WiiDeviceDriver class for Windows and Linux platforms.
 * It binds the wiiuse library and emits signals to the Qt app.
 *
 * This file is part of the Qt-based cross-platform Wii Balance Board driver.
 *
 * Copyright (C) 2009-2010, Dominic Stalder & Thomas Bruederli
 * Licensed under the GNU Lesser General Public License (LGPL) as published
 * by the Free Software Foundation at http://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * $Id: wiidevicedriver.cpp 5 2010-03-03 09:10:12Z roundcube $
 */

#include <iostream>
#include "wiidevicedriver.h"

// some math...
#define MAX(a,b) (a>b?a:b)

/**
 * This is the Win-specific implementation of the WiiBoard driver.
 * It connects with the WiiBoard class which relies on the wiiuse Framework.
 */

/**
 * Constructor of the board driver.
 *
 * Initializes the driver either for the wiiboard or the wiimote.
 *
 * @param  type  an integer specifying the device type
 */
WiiDeviceDriver::WiiDeviceDriver(int type) : deviceType(type), mywiimote(NULL)
{
	// sets the connection state of the wii device to NONE
	setConnectionState(WiiDeviceDriver::STATE_NONE);
}

/**
 * Destructor
 */
WiiDeviceDriver::~WiiDeviceDriver()
{
	stop();
	wait();
}

/**
 * Start the device driver
 *
 */
void WiiDeviceDriver::start()
{
	// get WiiuseConnector thread and start it if necessary
	WiiuseConnector* conn = WiiuseConnector::getInstance();

	if (!conn->isRunning())
		conn->start();

	setInstruction(WiiDeviceDriver::MESSAGE_CONNECTWINDOWS);
	if (!conn->requestWiimote(this))
		std::cerr << "WiiuseConnector busy!" << std::endl;
}

/**
 * Thread run-loop
 */
void WiiDeviceDriver::run()
{
	// only here for compatibility
}

/**
 * Thread wait method
 */
void WiiDeviceDriver::wait()
{
	// wait for WiiuseConnector
	WiiuseConnector* conn = WiiuseConnector::getInstance();
	std::cout << "Waiting for WiiuseConnector..." << std::endl;
	conn->wait();
}

/**
 * QThread method
 */
int WiiDeviceDriver::isRunning()
{
	// wrapper for WiiuseConnector thread
	WiiuseConnector* conn = WiiuseConnector::getInstance();
	return conn->isRunning();
}

/**
 * Thread stop method.
 *
 * Stopps connecting with the wii device.
 */
void WiiDeviceDriver::stop()
{
	// disconnect from WiiuseConnector
	WiiuseConnector* conn = WiiuseConnector::getInstance();
	conn->disconnect(this);
	conn->wait();

	setConnectionState(WiiDeviceDriver::STATE_NONE);
}

/**
 * Callback when WiiuseConnector found a matching device
 */
void WiiDeviceDriver::wiimoteConnect(wiimote* wm)
{
	mywiimote = wm;
	std::cout << "Device found! Connection established..." << std::endl;

	// set the connection state of the wii device to CONNECTED
	setConnectionState(WiiDeviceDriver::STATE_CONNECTED);

	// display message about successful connection
	setInstruction(WiiDeviceDriver::MESSAGE_SUCCESSFUL);

	// enable motion sensing (could be controlled by some flags)
	wiiuse_motion_sensing(wm, 1);
}

/**
 * Callback method for wiiuse_poll() events
 */
void WiiDeviceDriver::handleEvent(int event)
{
	// checks for events
	switch (event)
	{
	case WIIUSE_EVENT:
		// process event of a balance board
		if (deviceType == WiiDeviceDriver::TYPE_BALANCEBOARD)
		{
			if (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_A))	setButtonPressed(WiiDeviceDriver::BUTTON_A, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_A))	setButtonPressed(WiiDeviceDriver::BUTTON_A, 0);

			setKilograms((float)mywiimote->exp.bb.tl,
			             (float)mywiimote->exp.bb.tr,
			             (float)mywiimote->exp.bb.br,
			             (float)mywiimote->exp.bb.bl);
		}
		// process events for controller
		else
		{
			// send acceleration events
			if (WIIUSE_USING_ACC(mywiimote))
				setAcceleration((double)mywiimote->orient.roll, (double)mywiimote->orient.pitch, (double)mywiimote->orient.yaw);

			// currently only button events are checked
			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_A))	setButtonPressed(WiiDeviceDriver::BUTTON_A, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_A))	setButtonPressed(WiiDeviceDriver::BUTTON_A, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_B))	setButtonPressed(WiiDeviceDriver::BUTTON_B, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_B))	setButtonPressed(WiiDeviceDriver::BUTTON_B, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_UP))	setButtonPressed(WiiDeviceDriver::BUTTON_UP, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_UP))	setButtonPressed(WiiDeviceDriver::BUTTON_UP, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_RIGHT))	setButtonPressed(WiiDeviceDriver::BUTTON_RIGHT, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_RIGHT))	setButtonPressed(WiiDeviceDriver::BUTTON_RIGHT, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_DOWN))	setButtonPressed(WiiDeviceDriver::BUTTON_DOWN, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_DOWN))	setButtonPressed(WiiDeviceDriver::BUTTON_DOWN, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_LEFT))	setButtonPressed(WiiDeviceDriver::BUTTON_LEFT, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_LEFT))	setButtonPressed(WiiDeviceDriver::BUTTON_LEFT, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_ONE))	setButtonPressed(WiiDeviceDriver::BUTTON_1, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_ONE))	setButtonPressed(WiiDeviceDriver::BUTTON_1, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_TWO))	setButtonPressed(WiiDeviceDriver::BUTTON_2, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_TWO))	setButtonPressed(WiiDeviceDriver::BUTTON_2, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_MINUS))	setButtonPressed(WiiDeviceDriver::BUTTON_MINUS, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_MINUS))	setButtonPressed(WiiDeviceDriver::BUTTON_MINUS, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_PLUS))	setButtonPressed(WiiDeviceDriver::BUTTON_PLUS, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_PLUS))	setButtonPressed(WiiDeviceDriver::BUTTON_PLUS, 0);

			if  (IS_JUST_PRESSED(mywiimote, WIIMOTE_BUTTON_HOME))	setButtonPressed(WiiDeviceDriver::BUTTON_HOME, 1);
			else if (IS_RELEASED(mywiimote, WIIMOTE_BUTTON_HOME))	setButtonPressed(WiiDeviceDriver::BUTTON_HOME, 0);
		}
		break;

		case WIIUSE_DISCONNECT:
		case WIIUSE_UNEXPECTED_DISCONNECT:
			// sets the connection state of the wii device to UNCONNECTED
			setInstruction(WiiDeviceDriver::MESSAGE_LOSTCONNECTION);
			setConnectionState(WiiDeviceDriver::STATE_NOTCONNECTED);
			break;

		default:
			break;
	}
}

/**
 * Stop the run-loop
 */
/**
 * Returns true if the wii device is connected.
 *
 * @return  connection state of the wii device
 */
int WiiDeviceDriver::isConnected()
{
	return deviceState.connectionState == WiiDeviceDriver::STATE_CONNECTED;
}

/**
 * Callback method for displaying user instruction
 */
void WiiDeviceDriver::setInstruction(const unsigned char instr)
{
	// emit signal
	emit instructionChanged(deviceType, messageString(instr));
}

/**
 * Emits the signal that a log message has been generated.
 *
 * @param  logMessage  text of the log message as a char
 */
void WiiDeviceDriver::setLogMessage(QString logMessage)
{
	// emits signal that a log message has been generated
	emit logMessageGenerated(QString(logMessage), deviceType, deviceState.connectionState);
}

/**
 * Callback method to notice connection status changes.
 *
 * @param  state  connection state of the wii device
 */
void WiiDeviceDriver::setConnectionState(int state)
{
	// sets the connection state
	this->deviceState.connectionState = state;

	// emits a signal
	emit connectionStateChanged(deviceType, state);

	// sends a log message
	setLogMessage(tr("WiiDeviceDriver changed state."));
}

/**
 * Getter for current state
 */
WiiDeviceState& WiiDeviceDriver::getDeviceState()
{
	return deviceState;
}

/**
 * Callback method to get button events
 */
void WiiDeviceDriver::setButtonPressed(int which, int on)
{
	deviceState.buttonPressed = on ? which : 0;

	// DEUBG:
	std::cout << "Button " << which << (on ? " pressed" : " released") << std::endl;

	// emit signal
	emit buttonChanged(which, on);
}


/**
 * Callback method to get updated weight sensor values
 */
void WiiDeviceDriver::setKilograms(float topLeft, float topRight, float bottomRight, float bottomLeft)
{
	deviceState.topLeft = MAX(0, topLeft);
	deviceState.topRight = MAX(0, topRight);
	deviceState.bottomRight = MAX(0, bottomRight);
	deviceState.bottomLeft = MAX(0, bottomLeft);
	deviceState.totalWeight = deviceState.topLeft + deviceState.topRight + deviceState.bottomRight + deviceState.bottomLeft;

	// set zeros for very low sensor values
	if (deviceState.totalWeight < 5)
		deviceState.topLeft = deviceState.topRight = deviceState.bottomRight = deviceState.bottomLeft = deviceState.totalWeight = 0;

	// emit signal
	emit kilogramsChanged(deviceState.topLeft, deviceState.topRight, deviceState.bottomRight, deviceState.bottomLeft, deviceState.totalWeight);
}

/**
 * Callback for (changed) acceleration data
 */
void WiiDeviceDriver::setAcceleration(double roll, double pitch, double yaw)
{
	// emit signal with values in range -180..180
	emit orientationChanged(roll, pitch, yaw);
}
